#pragma once

#include <iostream>
#include <string>
#include <sys/select.h>
#include "Socket.hpp"
#include "Log.hpp"
#include <memory>

using namespace std;

const static uint16_t defalutport = 8888;
const static int gbacklog = 8;
const static int num = sizeof(fd_set) * 8;

class SelectServer
{
private:
    void HandlerEvent(fd_set &rfds)
    {
        for (int i = 0; i < num; i++)
        {
            // 是否监控
            if (!_rfds_array[i])
                continue;

            // 是否就绪
            int fd = _rfds_array[i]->GetSockFd();

            if (FD_ISSET(fd, &rfds))
            {
                // 是新连接到来，还是新数据到来
                // 新连接到来
                if (FD_ISSET(_listensock->GetSockFd(), &rfds))
                {
                    lg.LogMessage(Info, "get a new link\n");

                    string clientip;
                    uint16_t cilentport;
                    // 由于select已经检测到listensock已经就绪了，这里不会阻塞
                    Socket *sock = _listensock->AcceptConnection(&clientip, &cilentport);
                    if (!sock)
                    {
                        lg.LogMessage(Error, "accept error\n");
                        continue;
                    }
                    lg.LogMessage(Info, "get a client , client info# %s %d , fd:%d\n", clientip.c_str(), cilentport, sock->GetSockFd());

                    // 这里已经获取连接成功，由于底层数据不一定就绪
                    // 所以这里需要将新连接的文件描述符交给select托管
                    // 只需将文件描述符加入到_rfds_array即可
                    int pos = 0;
                    for (; pos < num; pos++)
                    {
                        if (_rfds_array[pos] == nullptr)
                        {
                            _rfds_array[pos] = sock;
                            break;
                        }
                    }

                    // fd_set能够存储的文件描述符是有上限的
                    if(pos == num)
                    {
                        sock->CloseSockFd();
                        delete sock;
                        lg.LogMessage(Warning, "server is full...!\n");
                    }
                }
                else
                {  // 是新数据来了
                    // 这里读是有问题的
                    string buffer;
                    bool flag = _rfds_array[i]->Recv(buffer,1024);
                    if(flag)  // 读取成功
                    {
                        lg.LogMessage(Info,"client say# %s\n",buffer.c_str());
                    }
                    else  // 读取失败
                    {
                        lg.LogMessage(Warning,"cilent quit !! close fd : %d\n",fd);
                        _rfds_array[i]->CloseSockFd();
                        delete _rfds_array[i];
                        _rfds_array[i] = nullptr;
                    }
                }
            }
        }
    }

public:
    SelectServer(uint16_t port = defalutport)
        : _port(port), _listensock(new TcpSocket()), _isrunning(false)
    {
    }

    void Init()
    {
        _listensock->BuildListenSocketMethod(_port, gbacklog);
        for (int i = 0; i < num; i++)
        {
            _rfds_array[i] = nullptr;
        }
        _rfds_array[0] = _listensock.get();
    }

    void Loop()
    {
        _isrunning = true;
        while (_isrunning)
        {
            fd_set rfds;
            FD_ZERO(&rfds);

            PrintDebug();

            // 设置需要监控的读事件文件描述符集，并找到最大的文件描述符
            int max_fd = _listensock->GetSockFd();
            for (int i = 0; i < num; i++)
            {
                if (_rfds_array[i] == nullptr)
                    continue;
                else
                {
                    int fd = _rfds_array[i]->GetSockFd();
                    // rfds本质是一个输入输出型参数，rfds是在select调用返回的时候，不断被修改，所以，每次都要重置
                    FD_SET(fd, &rfds);
                    if (max_fd < fd)
                    {
                        max_fd = fd;
                    }
                }
            }

            struct timeval timeout = {0, 0};
            ssize_t n = select(max_fd + 1, &rfds, nullptr, nullptr, &timeout);

            switch (n)
            {
            case -1:
            {
                lg.LogMessage(Fatal, "select Error , last time : %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                break;
            }
            case 0:
            {
                lg.LogMessage(Info, "select timeout... , last time : %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                break;
            }
            default:
            {
                lg.LogMessage(Info, "select success , begin handler event , last time : %u.%u\n", timeout.tv_sec, timeout.tv_usec);
                HandlerEvent(rfds);
                break;
            }
            }
        }
        _isrunning = false;
    }

    void Stop()
    {
        _isrunning = false;
    }

    // 查看当前哪些文件描述符需要被监控
    void PrintDebug()
    {
        std::cout << "current select rfds list is : ";
        for (int i = 0; i < num; i++)
        {
            if (_rfds_array[i] == nullptr)
                continue;
            else
                std::cout << _rfds_array[i]->GetSockFd() << " ";
        }
        std::cout << std::endl;
    }

    ~SelectServer() {}

private:
    unique_ptr<Socket> _listensock;
    uint16_t _port;
    bool _isrunning;

    // Select服务器需要所有的fd以数据结构的方式组织起来
    Socket *_rfds_array[num];
};